#!/usr/bin/env python
#
#    Copyright (C) 2006 Jonathan Saggau                                                
#    saggau@gmail.com                                                                        
#                                                                                                 
#    This program is free software; you can redistribute it and/or modify  
#    it under the terms of the GNU General Public License as published by  
#    the Free Software Foundation; either version 2 of the License, or      
#    (at your option) any later version.                                              
#                                                                                                 
#    This program is distributed in the hope that it will be useful,         
#    but WITHOUT ANY WARRANTY; without even the implied warranty of          
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            
#    GNU General Public License for more details.                                  
#                                                                                                 
#    You should have received a copy of the GNU General Public License      
#    along with this program; if not, write to the                                 
#    Free Software Foundation, Inc.,                                                    
#    59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.  
#
import os
import time

class Decoder(object):
    """Decodes the messages from the scServer"""
    def __init__(self):
        """The last message to the server indicates the type of response
        we'll get from the server; if the activeListener is On, we have to run
        a few tests to figure out which type of message was sent last..."""
        self.simpleCmds = {
                             '/fail': ['commandFullString', 'reason'],
                             'status.reply': ['meaninglessInt',
                                              'numberOfUgens',
                                              'numberOfSynths',
                                              'numberOfgroups',
                                              'numberOfLoadedSynthDefs',
                                              'averageCPU',
                                              'peakCPU', 'targetSampleRate',
                                              'actualSampleRate'],
                             '/late': ['timeStampSeconds',
                                       'timeStampFracSecond',
                                       'timeExecutedSeconds', 
                                       'timeExecutedFracSecond'],
                             '/synced': ['syncID'],
                             '/n_go':['nodeNumber', 'nodesParentGroupID',
                                          'previousNodeID', 'nextNodeID',
                                          'isGroupOrSynth', 
                                          'theIDOfTheHeadNode',
                                          'theIDOfTheTailNode'],
                             '/n_end':['nodeNumber', 'nodesParentGroupID',
                                          'previousNodeID', 'nextNodeID',
                                          'isGroupOrSynth', 
                                          'theIDOfTheHeadNode',
                                          'theIDOfTheTailNode'],
                             '/n_off':['nodeNumber', 'nodesParentGroupID',
                                          'previousNodeID', 'nextNodeID',
                                          'isGroupOrSynth', 
                                          'theIDOfTheHeadNode',
                                          'theIDOfTheTailNode'],
                             '/n_on':['nodeNumber', 'nodesParentGroupID',
                                          'previousNodeID', 'nextNodeID',
                                          'isGroupOrSynth', 
                                          'theIDOfTheHeadNode',
                                          'theIDOfTheTailNode'],
                             '/n_move':['nodeNumber', 'nodesParentGroupID',
                                          'previousNodeID', 'nextNodeID',
                                          'isGroupOrSynth', 
                                          'theIDOfTheHeadNode',
                                          'theIDOfTheTailNode'],
                             '/n_info':['nodeNumber', 'nodesParentGroupID',
                                          'previousNodeID', 'nextNodeID',
                                          'isGroupOrSynth', 
                                          'theIDOfTheHeadNode',
                                          'theIDOfTheTailNode'],
                             '/tr':['nodeID', 'triggerID', 'triggerValue'],
                             '/b_info':['BufferNumber','numberofFrames',
                                        'numberOfChannels','sampleRate']
                         }
  
    def parseMsg(self, inMsg):
        """returns a dictionary where the keys indicate what each bit of info 
        is and the values are the info values"""
        outKeysList = []        
        msgLength = len(inMsg)
        print inMsg
        time.sleep(1)
        if msgLength > 1:
            msgHead = inMsg[0]
            msgTail = inMsg[1:]
            return self._parseMsg(msgHead, msgTail)
        elif msgLength == 0 or msgLength == 1:
            return {}        
        
    def _parseMsg(self, head, tail):
        """Figures out what's actually in the msg."""
        msgHead = head
        msgTail = tail
        outDict = {}
        simpleCmds = self.simpleCmds

        def nested(modulus, messageTail):
            msgTail = messageTail
            miniCounter = littleCounter = 0
            lengthOfTail = len(msgTail)
            while lengthOfTail > 0:
                key = modulus[littleCounter%len(modulus)] + str(miniCounter)
                outDict[key] = msgTail[0]
                msgTail = msgTail[1:]
                lengthOfTail = len(msgTail)
                key = modulus[(littleCounter + 1) %len(modulus)] + \
                               str(miniCounter)
                outDict[key] = msgTail[0]
                msgTail = msgTail[1:]
                lengthOfTail = len(msgTail)
                littleCounter += 2
                miniCounter += 1
            
        if simpleCmds.has_key(msgHead):
            cmdKeys = simpleCmds[msgHead]
            for counter in range(len(msgTail)):
                key = cmdKeys[counter]
                value = msgTail[counter]
                outDict[key] = value
                
        elif msgHead == '/done':
            outDict['whatWasDone'] = msgTail[0]
            msgTail = msgTail[1:]
            if not len(msgTail) == 0:
                outDict['rest'] = msgTail[1:]
                
        elif msgHead == '/n_set':
            outDict['NodeId'] = msgTail[0]
            msgTail = msgTail[1:]
            modulus = ['controlName', 'controlValue']
            nested(modulus, msgTail)
                
        elif msgHead == '/n_setn':
            outDict['controlName'] = msgTail[0]
            msgTail = msgTail[1:]
            outDict['numberOfSequentialContsToChange'] = msgTail[0]
            msgTail = msgTail[1:]
            outDict['controlValues'] = msgTail
            
        elif msgHead == '/b_query':
            modulus = ['bufferNumber', 'numberOfFrames','numberOfchannels',\
                          'samplerate']
            nested(modulus, msgTail)
            
        elif msgHead == '/b_set':
            outDict['bufferNumber'] = msgTail
            msgTail = msgTail[1:]
            modulus = ['sampleIndex','sampleValue']
            nested(modulus, msgTail)
            
        elif msgHead == '/b_setn':
            outDict['bufferNumber'] = msgTail[0]
            msgTail = msgTail[1:]
            lenMsgTail = len(msgTail)
            counter = 0
            while lenMsgTail > 0:
                key = 'startingIndex%s' % counter
                outDict[key] = msgTail[0]
                msgTail = msgTail[1:]
                lenMsgTail = len(msgTail)
                
                key = 'NumberOfSamplesToChange%s' % counter
                outDict[key] = msgTail[0]
                msgTail = msgTail[1:]
                lenMsgTail = len(msgTail)

                takeLength = int(outDict[key])
                key = 'sampleValues%s' % counter
                outDict[key] = msgTail[:takeLength]
                msgTail = msgTail[takeLength:]
                lenMsgTail = len(msgTail)
                counter+=1
            
        elif msgHead == '/c_set':
            modulus = ['busIndex', 'controlValue']
            nested(modulus, msgTail)
            
        elif msgHead == '/c_setn':
            lenMsgTail = len(msgTail)
            counter = 0
            while lenMsgTail > 0:
                key = 'startingBusIndex%s' % counter
                outDict[key] = msgTail[0]
                msgTail = msgTail[1:]
                lenMsgTail = len(msgTail)
                
                key = 'numberOfSequentialBussesToChange%s' % counter
                outDict[key] = msgTail[0]
                msgTail = msgTail[1:]
                lenMsgTail = len(msgTail)

                takeLength = int(outDict[key])
                key = 'controlValues%s' % counter
                outDict[key] = msgTail[:takeLength]
                msgTail = msgTail[takeLength:]
                lenMsgTail = len(msgTail)
                counter+=1
                          
        else:
            raise KeyError, "message not found %s %s" %(head, tail)
        
        outDict["head"] = msgHead
        return outDict
        
    def decode(self, msg):
        return self.parseMsg(msg)
 
    def printPretty(self, parsed):
        """returns a pretty string representation of a parsed msg"""
        outStr = []
        outStr.append(':::Message Type:: %s%s' %(parsed['head'],os.linesep))
        for each in parsed.keys():
            if each == 'msgNotFound':
                print "WARNING: Message Definition not found"
            outStr.append(' :: '.join([str(each), 
                          str(parsed[each])+os.linesep]))
        print ''.join(outStr)                 

def test():
    decoder = Decoder()
    print decoder.parseMsg(['status.reply', 1, 0, 0, 1, 58, 
                            0.41983491182327271, 0.96434295177459717])
    print "___________"
    decoder.printPretty(decoder.parseMsg(['/n_end', 1003, 0, -1, -1, 0]))
    print "___________"
    decoder.printPretty(decoder.parseMsg(['/n_set', 1003,
                                          '/this', -1, '/that', 0]))
    print "___________"    
    decoder.printPretty(decoder.parseMsg(['/b_setn', 1, '/this', 3, 1, 2, 3,
                                          '/that', 4, 4,5,6,7]))
    print "___________"  
    decoder.printPretty(decoder.parseMsg(['/b_info', 1100, 0, 0, 0.0]))
    print "___________"
    print "YOU SHOULD GET AN EXCEPTION HERE, THIS IS NOT A BUG"
    decoder.printPretty(decoder.parseMsg(['/n_FAKE', 1003, 0, -1, -1, 0]))

if __name__ == '__main__':
    test()